package com.yxsk.relay.job.component.endpoint.handle.context;

import com.yxsk.relay.job.component.common.utils.CollectionUtils;
import com.yxsk.relay.job.component.endpoint.exception.bean.JobHandlerBeanException;
import com.yxsk.relay.job.component.endpoint.exception.facade.JobExecutorNotFoundException;
import com.yxsk.relay.job.component.endpoint.exception.facade.RelayJobConfigException;
import com.yxsk.relay.job.component.endpoint.handle.JobExecutor;
import com.yxsk.relay.job.component.endpoint.handle.anno.JobHandler;
import com.yxsk.relay.job.component.endpoint.utils.ClassScanUtils;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Stream;

/**
 * @Description
 * @Author 11376
 * @CreateTime 2019/9/9 19:34
 */
@Slf4j
@UtilityClass
public class RelayJobExecutorManager {

    private Map<String, ExecutorProperty> EXECUTOR_PROPERTY_CONFIG = new HashMap<>();

    private ReadWriteLock LOCK = new ReentrantReadWriteLock(false);

    private JobExecutorBeanFactory executorBeanFactory = new DefaultJobExecutorBeanFactory();

    public static void setExecutorBeanFactory(JobExecutorBeanFactory executorBeanFactory) {
        RelayJobExecutorManager.executorBeanFactory = executorBeanFactory;
    }

    public void load(String[] basePackages) {
        // 扫描类
        if (basePackages != null) {
            Stream.of(basePackages).forEach(packageName -> {
                List<Class<?>> classes = ClassScanUtils.scanClasses(packageName, JobExecutor.class);
                if (CollectionUtils.isNotEmpty(classes)) {
                    classes.stream().forEach(clazz -> addExecutor((Class<JobExecutor>) clazz));
                }
            });
        }
    }

    public void load(Class<JobExecutor>[] executors) {
        if (executors != null && executors.length > 0) {
            Stream.of(executors).forEach(executor -> addExecutor(executor));
        }
    }

    public JobExecutor getNewExecutor(String jobHandlerName) {
        LOCK.readLock().lock();
        ExecutorProperty executorProperty;
        try {
            executorProperty = EXECUTOR_PROPERTY_CONFIG.get(jobHandlerName);
        } finally {
            LOCK.readLock().unlock();
        }
        if (executorProperty == null) {
            throw new JobExecutorNotFoundException(MessageFormat.format("Not found executor named [{0}]", jobHandlerName));
        }

        return executorBeanFactory.createInstance(executorProperty.executorClass, executorProperty.scope);
    }

    private void addExecutor(Class<JobExecutor> executorClass) {
        JobHandler jobHandler = executorClass.getAnnotation(JobHandler.class);
        if (jobHandler != null) {
            LOCK.writeLock().lock();
            try {
                ExecutorProperty property = EXECUTOR_PROPERTY_CONFIG.get(jobHandler.value());
                if (property != null && property.executorClass != executorClass) {
                    throw new RelayJobConfigException(MessageFormat.format("Find the same job handle name of [{0}], classes: [%s], [%s]", jobHandler.value(), property.executorClass.getName(), executorClass.getName()));
                }

                property = new ExecutorProperty();
                property.executorClass = executorClass;
                property.scope = jobHandler.scope();

                EXECUTOR_PROPERTY_CONFIG.put(jobHandler.value(), property);

                if (log.isDebugEnabled()) {
                    log.debug("Load the job handle named [{}], mapping executor [{}]", jobHandler.value(), executorClass.getName());
                }
            } finally {
                LOCK.writeLock().unlock();
            }
        } else if (log.isDebugEnabled()) {
            log.debug("Jobhandle annotation of [{}] class not found, skip.", executorClass.getName());
        }
    }

    private class ExecutorProperty {

        Class<? extends JobExecutor> executorClass;

        JobHandler.Scope scope;

    }

    public interface JobExecutorBeanFactory {

        JobExecutor createInstance(Class<? extends JobExecutor> executorClass, JobHandler.Scope scope) throws JobHandlerBeanException;

    }

    private static class DefaultJobExecutorBeanFactory implements JobExecutorBeanFactory {

        private Map<Class, JobExecutor> SINGLETON_INSTANCE_CONTAINER = new ConcurrentHashMap<>();

        @Override
        public JobExecutor createInstance(Class<? extends JobExecutor> executorClass, JobHandler.Scope scope) {
            if (JobHandler.Scope.Prototype == scope) {
                // 原型模式
                try {
                    return createNewExecutor(executorClass);
                } catch (IllegalAccessException | InstantiationException e) {
                    throw new JobHandlerBeanException(e);
                }
            } else if (JobHandler.Scope.Singleton == scope) {
                // 单例模式
                JobExecutor jobExecutor = SINGLETON_INSTANCE_CONTAINER.get(executorClass);
                if (jobExecutor == null) {
                    // 创建单例
                    synchronized (RelayJobExecutorManager.class) {
                        jobExecutor = SINGLETON_INSTANCE_CONTAINER.get(executorClass);
                        if (jobExecutor == null) {
                            try {
                                jobExecutor = createNewExecutor(executorClass);
                            } catch (IllegalAccessException | InstantiationException e) {
                                throw new JobHandlerBeanException(e);
                            }
                            SINGLETON_INSTANCE_CONTAINER.put(executorClass, jobExecutor);
                        }
                    }
                    return jobExecutor;
                }
            }
            throw new JobHandlerBeanException("Unsupported instance mode (" + scope + ")");
        }

        private JobExecutor createNewExecutor(Class<? extends JobExecutor> executorClass) throws IllegalAccessException, InstantiationException {
            return executorClass.newInstance();
        }
    }

}
